<?php
/**
 * JoosFish - Multi Lingual extention and translation manager for Joostina CMS
 * Copyright (C) 2011 Joostina Team
 * 
 * @name JoosFish
 * @copyright © 2011 Joostina team, © 2003-2007 Think Network GmbH, Munich
 * @license GNU General Public License version 2
 * @version 1.8.2.1
 * @package Joostina
 *
 * Based on the source code of Joom!Fish 1.8.2 copyrighted by Think
 * Network GmbH, Munich. The main contribution is made by developers of
 * Joom!Fish project, but the main reason behind making JoosFish an alongside
 * project is to maintain it's compatibility with Joostina CMS 1.3.x
 * 
 * For full copyright note see documetation/COPYRIGHT.php
 * 
 * For changes see documentation/CHANGELOG.php
 *
 * The "GNU General Public License" (GPL) is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * -----------------------------------------------------------------------------
 * $Id: mldatabase.class.php 8 2011-06-30 08:23:14Z hnatt88@gmail.com $
 *
*/


// Don't allow direct linking
defined( '_VALID_MOS' ) or die( 'Direct Access to this location is not allowed.' );


require_once( mosMainFrame::getBasePath() .'/components/com_joomfish/includes/defines.php' );
require_once( JOOMFISH_LIBPATH .DS. 'joomla' .DS. 'language.php' );
require_once( JOOMFISH_LIBPATH .DS. 'joomla' .DS. 'registry.php' );

require_once( mosMainFrame::getBasePath().'/components/com_joomfish/includes/joomfish.class.php' );
require_once( mosMainFrame::getBasePath()."/administrator/components/com_joomfish/joomfish.class.php");

$joomFishManager =& JoomFishManager::getInstance( dirname( __FILE__ ) );


/**
 * Multi lingual Database connector class
 *
 * This extention of the standard database class converts the output of the query automatically
 * with the actual selected language in the web site.
 *
 * @package joomfish
 * @subpackage database
 * @copyright 2003-2007 Think Network GmbH
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @version 1.0, 2003-10-16 $Revision: 781 $
 * @author Alex Kempkens <joomfish@thinknetwork.com>
*/
class mlDatabase extends database {

	/** @var array list of multi lingual tables */
	var $_mlTableList=null;
	/** @var Internal variable to hold array of unique tablenames and mapping data*/
	var $_refTables=null;


	/** Constructor
	*/
	function mlDatabase( $host='localhost', $user, $pass, $db, $table_prefix='jos_', $driver='' ) {
		$this->database( $host, $user, $pass, $db, $table_prefix, $driver );

		$query = "select distinct reference_table from #__jf_content";
		$this->setQuery( $query );
		$this->_mlTableList = $this->loadResultArray();
		if( !$this->_mlTableList ){
			echo $this->getErrorMsg();
		}
	}

	/**
	* This global function loads the first row of a query into an object
	*
	* If an object is passed to this function, the returned row is bound to the existing elements of <var>object</var>.
	* If <var>object</var> has a value of null, then all of the returned query fields returned in the object.
	* @param string The SQL query
	* @param object The address of variable
	*/
	function loadObject( &$object, $translate=true, $language=null ) {
		global $mosConfig_lang, $_JOOMFISH_MANAGER;
		if( $language===null) $language = $mosConfig_lang;

		$result = parent::loadObject( $object );
		if( $translate && isset($_JOOMFISH_MANAGER)) {
			$doTranslate=false;
			$tables =$this->_getRefTables();
			foreach ($tables["fieldTablePairs"] as $i=>$table) {
				if ($this->translatedContentAvailable($table)) {
					$doTranslate=true;
					break;
				}
			}
			if ( $doTranslate) {
				JoomFish::translate( $object, $this->_getTableName(), $language, $this->_getRefTables());
			}
		}
		return $result;
	}


	/**
	 * Public function to test if table has translated content available
	 *
	 * @param string $table : tablename to test
	 * @return boolean	true if content is available
	 */
	function translatedContentAvailable($table){
		$result = false;
		if( $this->_mlTableList != null && is_array($this->_mlTableList) ) {
			$result = in_array( $table, $this->_mlTableList);
		}
		$result = ($table=="content" ? true : $result);

		return $result;
	}

	/**
	* Load a list of database objects
	* @param string The field name of a primary key
	* @param boolean	$translate	indecates if the query should automatically be translated or not
	* @param string		$language	indecates the language into which the translation should be done
	* @return array If <var>key</var> is empty as sequential list of returned TRANSLATED records.
	* If <var>key</var> is not empty then the returned array is indexed by the value
	* the database key.  Returns <var>null</var> if the query fails.
	*/
	function loadObjectList( $key='', $translate=true, $language=null ) {
		global $mosConfig_lang, $_JOOMFISH_MANAGER;
		if( $language===null) $language = $mosConfig_lang;

		$result = parent::loadObjectList( $key );
		if( $translate && isset($_JOOMFISH_MANAGER)) {
			$doTranslate=false;
			$tables =$this->_getRefTables();
			if ($tables == null) return $result; // an unstranslatable query to return result as is
			foreach ($tables["fieldTablePairs"] as $i=>$table) {
				if ($this->translatedContentAvailable($table)) {
					$doTranslate=true;
					break;
				}
			}
			if ($doTranslate ) {
				JoomFish::translateList( $result, $this->_getTableName(), $language, $this->_getRefTables() );
			}
		}
		return $result;
	}

	function loadAssocList($key=''){
		$objects = $this->loadObjectList($key);
		if ($objects && count($objects)>0) {
			foreach ($objects as $key=>$val) {
				$objects[$key] = get_object_vars($val);
			}
		}
		return $objects;
	}

	/**
	* Overwritten method to loads the first field of the first row returned by the query.
	*
	* @return The value returned in the query or null if the query failed.
	*/
	function loadResult( $translate=true, $language=null ) {
		$result=null;
		$ret=null;
		$this->loadObject( $result, $translate, $language );
		if( $result != null ) {
			$fields = get_object_vars( $result );
			$field = each($fields);
			$ret = $field[1];
		}
		return $ret;
	}

	/**
	* Overwritten insert function to enable storage of material created in non-default language.
	* Note that this creates a translation which is identical to the original - when we update
	* the original in the default language we then keep the translation (although it will appread out of date!).
	*
	* @param	string	table name
	* @param	object	instance with information to store
	* @param	string	primary key name of table
	* @param	boolean	debug info printed or not
	* @param	boolean	passthru without storing information in a translation table
	*/
	function insertObject( $table, &$object, $keyName = NULL, $verbose=false , $passthru=false) {
		global $mosConfig_lang, $mosConfig_defaultLang, $_JOOMFISH_MANAGER;
		$passthru = $mosConfig_lang == $mosConfig_defaultLang;

		if( !$passthru && isset($_JOOMFISH_MANAGER) && is_a($object, 'mosDBTable')) {
			//Must insert parent first to get reference id !
			$parentInsertReturn = parent::insertObject( $table, $object, $keyName, $verbose);
			$actContentObject=null;
			if( isset($table) && $table!="" ) {
				$tableName = preg_replace( '/^#__/', '', $table);
				// *** QUESTION ***//
				// IS THIS TEST APPROPRIATE HERE - I THINK IT MEANS YOU CAN'T DO A FIRST TRANSLATION FOR A TABLE VIA THE FRONT END
				// ** TEST BEFORE 1.8 **//
				//if ($this->translatedContentAvailable($table)) {
				$contentElement = $_JOOMFISH_MANAGER->getContentElement( $tableName );
				if( isset( $contentElement ) ) {
					$actContentObject = new ContentObject( $_JOOMFISH_MANAGER->getLanguageID($mosConfig_lang), $contentElement );
					if( isset( $object->$keyName ) ) {
						$actContentObject->loadFromContentID( $object->$keyName );
						$actContentObject->updateMLContent( $object );
						if( isset( $object->state ) ) {
							$actContentObject->published = ($object->state == 1) ? true : false;
						} else if ( isset( $object->published ) ) {
							$actContentObject->published = ($object->published == 1) ? true : false;
						}
						if ($actContentObject->published){
							if ( $_JOOMFISH_MANAGER->getCfg("frontEndPublish")){
								global $acl, $my;
								$access = new stdClass();
								$access->canPublish = $acl->acl_check( 'action', 'publish', 'users', $my->usertype, 'content', 'all' );
								if ($access->canPublish) $actContentObject->setPublished($actContentObject->published);
							}
						}
						$actContentObject->store();
					}
				}
				//}
			}
			return $parentInsertReturn;
		}
		else {
			return parent::insertObject( $table, $object, $keyName, $verbose);
		}
	}

	/**
	* Overwritten update function to enable storage of translated information.
	* Based on the configuration in the content elements the automatic storage of
	* information is activated or not. It is important that this upgraded method will ensure
	* that all information will be written into the translation table. Only in the case that the
	* default language is choosen the information will be updated directly within the original tables.
	* To make sure that all other information will be written into the tables as expected the
	* statements will be manipulated as needed.
	*
	* @param	string	table name
	* @param	object	instance with information to store
	* @param	string	primary key name of table
	* @param	boolean	update fields with null or not
	* @param	boolean	passthru without storing information in a translation table
	*/
	function updateObject( $table, &$object, $keyName, $updateNulls=true, $passthru=false ) {
		global $mosConfig_lang, $mosConfig_defaultLang, $_JOOMFISH_MANAGER;

		// if the currect language is the site default language the translations will not be updated!
		$passthru = $mosConfig_lang == $mosConfig_defaultLang;

		if( !$passthru && isset($_JOOMFISH_MANAGER) && is_a($object, 'mosDBTable')) {
			$actContentObject=null;
			if( isset($table) && $table!="" ) {
				$tableName = preg_replace( '/^#__/', '', $table);
				// *** QUESTION ***//
				// IS THIS TEST APPROPRIATE HERE - I THINK IT MEANS YOU CAN'T DO A FIRST TRANSLATION FOR A TABLE VIA THE FRONT END
				// ** TEST BEFORE 1.8 **//
				//if ($this->translatedContentAvailable($table)) {
				$contentElement = $_JOOMFISH_MANAGER->getContentElement( $tableName );
				if( isset( $contentElement ) ) {
					$actContentObject = new ContentObject( $_JOOMFISH_MANAGER->getLanguageID($mosConfig_lang), $contentElement );
					if( isset( $object->$keyName ) ) {
						$actContentObject->loadFromContentID( $object->$keyName );
						$actContentObject->updateMLContent( $object );
						if( isset( $object->state ) ) {
							$actContentObject->published = ($object->state == 1) ? true : false;
						} else if ( isset( $object->published ) ) {
							$actContentObject->published = ($object->published == 1) ? true : false;
						}
						if ( $_JOOMFISH_MANAGER->getCfg("frontEndPublish")){
							global $acl, $my;
							$access = new stdClass();
							$access->canPublish = $acl->acl_check( 'action', 'publish', 'users', $my->usertype, 'content', 'all' );
							if ($access->canPublish) $actContentObject->setPublished($actContentObject->published);
						}

						$actContentObject->store();
					}
				}
				//}
			}
			return parent::updateObject( $table, $object, $keyName, $updateNulls );

		} else {
			return parent::updateObject( $table, $object, $keyName, $updateNulls );
		}
	}

	/** Internal function to determit the table name from an sql query
	 *
	 * @return	string	table name
	 */
	function _getTableName() {
		global $mosConfig_dbprefix;

		$posFrom = strpos( strtoupper($this->_sql), 'FROM ') + 5; // after 'FROM '
		$posWhere = strpos( strtoupper($this->_sql), 'WHERE ');
		$table = substr( $this->_sql, $posFrom, $posWhere - $posFrom);
		if( strpos( $table, ' ' ) !== false ) {
			$table = substr( $table, 0, strpos( $table, ' ' ) );
		}
		if (isset($mosConfig_dbprefix) && strlen($mosConfig_dbprefix)>0) $table = str_replace( $mosConfig_dbprefix, '', $table);
		$table = str_replace( "\n", "", $table) ;

		return $table;
	}

	/**
	 * Override query in order to extract ref tables data
	 *
	 * @return n/a
	 */
	function query() {
		parent::query();
		$this->setRefTables();
		return $this->_cursor;
	}

	function fillRefTableCache($cacheDir,$cacheFile){
		$cacheFileContent = serialize($this->_refTables);
		$handle = fopen($cacheFile,"wb");
		fwrite($handle,$cacheFileContent);
		fclose($handle);
		// clean out old cache files
		// This could be very slow for long list of old files -
		// TODO store in database instead
		$this->cleanRefTableCache($cacheDir);
	}

	function cleanRefTableCache($cacheDir){
		if (!($dh = opendir($cacheDir))) {
			return false;
		}
		while ($file = readdir($dh)) {
			if (($file != '.') && ($file != '..')) {
				$file = "$cacheDir/$file";
				if (is_file($file) && @filemtime($file) < $this->cacheExpiry) {
					if (!@unlink($file)) {
						echo "problems clearing cache file $file";
					}
				}
			}
		}
		return true;
	}

	function _logSetRefTablecache($action,$tempsql,$sql_exNos,$sqlHash){
		$logfile = dirname(__FILE__)."/qalog.txt";
		$handle = fopen($logfile,"ab");
		// replace tabs and carriage returns with spaces
		fwrite($handle,"$action ");
		fwrite($handle,preg_replace("/([\t\n\r\f])/"," ",$tempsql));
		fwrite($handle," #@Ј^Ј@# ");
		fwrite($handle,preg_replace("/([\t\n\r\f])/"," ",$sql_exNos));
		fwrite($handle," #@Ј^Ј@# ");
		fwrite($handle,preg_replace("/([\t\n\r\f])/"," ",$sqlHash));
		fwrite($handle," # JF LINE END# \n");
		
		fclose($handle);

	}
	
	function setRefTables(){
		// Before joomfish manager is created since we can't translate so skip this anaylsis
		global $_JOOMFISH_MANAGER;
		if (!$_JOOMFISH_MANAGER) return;

		// This could be speeded up by the use of a cache - but only of benefit is global caching is off
		$tempsql = $this->_sql;
		// only needed for selects at present - possibly add for inserts/updates later
		if (strpos(strtoupper($tempsql),"SELECT")===false) {
			return;
		}

		if ($_JOOMFISH_MANAGER->getCfg("qacaching",0)){
			global $mosConfig_cachepath,$mosConfig_cachetime;
			// remove time formats (assume all numbers are not necessay - this is experimental
			// for example table names or column names could contain numbers
			// so this version only replaces numbers not adjacent to alpha characters i.e.field2 doesn't become field
			$sql_exNos = preg_replace("/(?![a-z])(.)([0-9]+)(?![a-z]+)/i",'${1}',$tempsql);
			$sql_exNos = preg_replace("/(?![a-z]).([0-9]+)$/i",'${1}',$sql_exNos);

			global $mosConfig_debug;
			if ($mosConfig_debug) {
				echo "<p style='background-color:bisque;border:solid 1px black'><strong>setRefTables debug:</strong><br / >"
				. "tempsql   = $tempsql<br />"
				. "sql_exNos = $sql_exNos"
				. "</p>";
			}

			$sqlHash = md5($sql_exNos );

			$this->cacheExpiry = time() - $mosConfig_cachetime;
			$cacheDir = "$mosConfig_cachepath/refTableSQL";
			if (!file_exists($cacheDir)) mkdir($cacheDir);
			$cacheFile = "$cacheDir/$sqlHash";
			if (file_exists($cacheFile) &&	@filemtime($cacheFile) > $this->cacheExpiry) {
				$cacheFileContent = file_get_contents($cacheFile);
				$this->_refTables = unserialize($cacheFileContent);
				
				if ($_JOOMFISH_MANAGER->getCfg("qalogging",0)){
					$this->_logSetRefTablecache("r",$tempsql,$sql_exNos,$sqlHash);					
				}
				return;
			}

			if($this->_cursor===true || $this->_cursor===false) {
				if ($_JOOMFISH_MANAGER->getCfg("qalogging",0)){
					$this->_logSetRefTablecache("wtf",$tempsql,$sql_exNos,$sqlHash);					
				}
				$this->fillRefTableCache($cacheDir,$cacheFile);
				return;
			}
		}
		// get column metadata
		$i = 0;
		//echo " $this->_sql $this->_cursor ".var_export( $this->_cursor, true )."<br>";
		if (is_object($this->_cursor) && get_class($this->_cursor)=="mysqli_result"){
		  $dbIsMySQL = false;
		}
		else {
			if (!is_resource($this->_cursor)){
				// This is a serious problem since we do not have a valid db connection
				// or there is an error in the query
				if ($this->getErrorMsg()===""){
					echo JText::_("No valid database connection")."<br/>";				
				}
				return;
			}
			$cursorType = get_resource_type($this->_cursor);
			if ($cursorType == "mysql result") {
				$dbIsMySQL = true;
			}
			else $dbIsMySQL = false;
		}

		if (!$dbIsMySQL && function_exists("mysqli_num_fields"))  $fields = mysqli_num_fields($this->_cursor);
		else $fields = mysql_num_fields($this->_cursor);
		//print "<br> $tempsql $this->_cursor $fields";

		if ($_JOOMFISH_MANAGER->getCfg("qacaching",0)){
			if ($fields<=0) {
				if ($_JOOMFISH_MANAGER->getCfg("qalogging",0)){
					$this->_logSetRefTablecache("w0f",$tempsql,$sql_exNos,$sqlHash);					
				}
				$this->fillRefTableCache($cacheDir,$cacheFile);
				return;
			}
		}

		$this->_refTables=array();
		$this->_refTables["fieldTablePairs"]=array();
		$this->_refTables["tableAliases"]=array();
		$this->_refTables["fieldAliases"]=array();
		$this->_refTables["fieldTableAliasData"]=array();
		$this->_refTables["fieldCount"]=$fields;
		$this->_refTables["sql"]=$tempsql;
		// local variable to keep track of aliases that have already been analysed
		$tableAliases = array();

		for ($i = 0; $i < $fields; ++$i) {
			if (!$dbIsMySQL && function_exists("mysqli_fetch_field"))  $meta = mysqli_fetch_field($this->_cursor);
			else  $meta = mysql_fetch_field($this->_cursor, $i);
			if (!$meta) {
				echo "No information available<br />\n";
			}
			else {
				$tempTable =  $meta->table;
				// if I have already found the table alias no need to do it again!
				if (array_key_exists($tempTable,$tableAliases)){
					$value = $tableAliases[$tempTable];
				}
				else {
					if (!isset($tempTable) || strlen($tempTable)==0) {
						continue;
					}
					//echo "<br>Information for column $i of ".($fields-1)." ".$meta->name." : $tempTable=";
					$tempArray=array();
					$prefix = $this->_table_prefix;
					preg_match_all("/$prefix(\w*)\s+AS\s+".$tempTable."[,\s]/i",$this->_sql, $tempArray, PREG_PATTERN_ORDER);
					if (count($tempArray)>1 && count($tempArray[1])>0) $value = $tempArray[1][0];
					else $value = null;
					if (isset($this->_table_prefix) && strlen($this->_table_prefix)>0 && strpos($tempTable,$this->_table_prefix)===0) $tempTable = substr($tempTable, strlen( $this->_table_prefix));
					$value = $value?$value:$tempTable;
					$tableAliases[$tempTable]=$value;
				}

				if ((!($value=="session" || strpos($value,"jf_")===0)) && $this->translatedContentAvailable($value)){
					/// ARGH !!! I must also look for aliases for fieldname !!
					$tempName = $meta->name;
					$tempArray=array();
					preg_match_all("/(\w*)\s+AS\s+".$tempName."[,\s]/i",$this->_sql, $tempArray, PREG_PATTERN_ORDER);
					if (count($tempArray)>1 && count($tempArray[1])>0) {
						//echo "$meta->name is an alias for ".$tempArray[1][0]."<br>";
						$nameValue = $tempArray[1][0];
					}
					else $nameValue = $meta->name;

					if (!array_key_exists($value,$this->_refTables["tableAliases"])) $this->_refTables["tableAliases"][$value]=$meta->table;
					// I can't use the field name as the key since it may not be unique!
					if (!array_key_exists($meta->table,$this->_refTables["fieldTablePairs"])){
						$this->_refTables["fieldTablePairs"][$meta->table]=$value;
					}

					//if (!in_array($value,$this->_refTables["fieldTablePairs"])) $this->_refTables["fieldTablePairs"][]=$value;
					if (!array_key_exists($nameValue,$this->_refTables["fieldAliases"])) $this->_refTables["fieldAliases"][$meta->name]=$nameValue;

					// Put all the mapping data together so that everything is in sync and I can check fields vs aliases vs tables in one place
					$this->_refTables["fieldTableAliasData"][$i]=array("fieldNameAlias"=>$meta->name, "fieldName"=>$nameValue,"tableNameAlias"=>$meta->table,"tableName"=>$value);
				}

			}
		}
		if ($_JOOMFISH_MANAGER->getCfg("qacaching",0)){
			if ($_JOOMFISH_MANAGER->getCfg("qalogging",0)){
				$this->_logSetRefTablecache("wn",$tempsql,$sql_exNos,$sqlHash);					
			}
			$this->fillRefTableCache($cacheDir,$cacheFile);
		}
	}

	/** Internal function to return reference table names from an sql query
	 *
	 * @return	string	table name
	 */
	function _getRefTables(){
		return $this->_refTables;
	}
}
?>
